use std::borrow::Cow;
use std::{fmt::Display, iter::Peekable};

type CowStr = Cow<'static, str>;

#[derive(Debug, Clone, PartialEq)]
pub struct Pos {
    pub row: usize,
    pub col: usize,
    pub file: CowStr,
}

impl Display for Pos {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}:{}", self.file, self.row, self.col)
    }
}

impl Pos {
    pub fn new(row: usize, col: usize, file: CowStr) -> Self {
        Self { row, col, file }
    }

    pub(crate) fn zero() -> Pos {
        Pos {
            row: 0,
            col: 0,
            file: Cow::Borrowed(""),
        }
    }
}

pub struct CharSeq<Iter>
where
    Iter: Iterator<Item = char>,
{
    iter: Peekable<Iter>,
    pos: Pos,
}

impl<Iter> CharSeq<Iter>
where
    Iter: Iterator<Item = char>,
{
    pub fn new(iter: Iter, file: CowStr) -> Self {
        Self {
            iter: iter.peekable(),
            pos: Pos::new(1, 0, file),
        }
    }

    pub fn pos(&self) -> Pos {
        self.pos.clone()
    }

    pub fn peek(&mut self) -> Option<&Iter::Item> {
        self.iter.peek()
    }

    pub fn next_if(&mut self, func: impl FnOnce(&Iter::Item) -> bool) -> Option<Iter::Item> {
        self.iter.next_if(func).inspect(|c| self.increase_pos(*c))
    }

    pub fn increase_pos(&mut self, c: char) {
        if c == '\n' {
            self.pos.row += 1;
            self.pos.col = 0;
        } else {
            self.pos.col += 1;
        }
    }
}

impl<Iter: Iterator<Item = char>> Iterator for CharSeq<Iter> {
    type Item = <Iter as Iterator>::Item;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().inspect(|c| self.increase_pos(*c))
    }
}
